//
// xmlapp.cpp
// xmlint
// An XML-based interface for the Protea Project / xmlapp.cpp implements the AppInterface object
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../SDK/ToolsInterface.h"
#include "xmlparse.h"
#include "comm_standard.h"
#include "../defns.h"
using namespace ProteaApp1;

XCTiming *timer = NULL;
XCTiming *overall = NULL;

//////////////////////////////////////////////////////////
//
// Helper functions
//
//////////////////////////////////////////////////////////

// Change a substring
//
// Replaces a substring in a string with another string, used to generate escape sequences for HTML
char* Replace( char* text, const char* oldstr, const char* newstr ) {
	char* temp;
	char* found;
	char* retval = text;
	long loc;

	long newsize = strlen( newstr );
	long oldsize = strlen( oldstr );

	found = strstr( retval, oldstr );
	while ( found ) {
		long retsize = strlen( retval );

		temp = new char[ retsize + newsize + 1 ];

		// Copy everything up to the replacement character
		loc = found - retval;
		memcpy( temp, retval, loc );

		// Copy in the replacement string
		memcpy( temp + loc, newstr, newsize );
		strcpy( temp + loc + newsize, found + oldsize );

		// Cleanup the previous string
		delete[] retval;
		retval = temp;

		found = strstr( retval + loc + newsize, oldstr );
	};

	return retval;
};



// Fix for translating to XML Escape sequences
//
// Makes certain characters into XML escape sequences
char* FixEscape( char* text )
{
	long len = strlen( text );
	char* retval = new char[ len + 1 ];
	strcpy( retval, text );

	// check for '&', '<', '>', and '"'
	retval = Replace( retval, "&", "&amp;" );	// do this one first, or it'll screw up the others...
	retval = Replace( retval, "<", "&lt;" );
	retval = Replace( retval, ">", "&gt;" );
	retval = Replace( retval, "\"", "&quot;" );
	retval = Replace( retval, "'", "&apos;" );
	return retval;
};




//////////////////////////////////////////////////////////
//
// XCXMLAppInterface
//
//////////////////////////////////////////////////////////

XCXMLAppInterface::XCXMLAppInterface( char* name, char* dispname, char* desc, void* func, char* deps ) : XCService( name, dispname, desc, func, deps )
{
	newsocket = NULL;
	listen_socket = NULL;
	listen_thread = NULL;
	app_list = NULL;
	socket_thread_list = NULL;
	trans_socket_list = NULL;

	XCAppOptions opts;
	GetOptions( opts );
	opts.no_database = true;
	opts.no_transactions = true;
//	opts.debug_type_filter = 0xFFFF;
//	opts.debug_type_filter = TRC_APPINTERFACE | TRC_BUFFER | TRC_NOLABEL | TRC_DATABASE | TRC_FUNCTIONS; 
//	opts.debug_type_filter = TRC_TRAFFIC | TRC_MESSAGING | TRC_FUNCTIONS | TRC_APPINTERFACE;
	opts.debug_type_filter = 0x0;
	for ( int i = 0; i < num_tc; i++ )
		opts.debug_level_cutoff[ i ] = TLV_LOW;
	SetOptions( opts );
};


XCXMLAppInterface::~XCXMLAppInterface() {
/*
	if (myinfo && myinfo->dest)
		delete myinfo->dest;
*/
};



void XCXMLAppInterface::Startup() {
	INFUNC( XCXMLAppInterface::Startup, this );

	// Connect to GCS
	GetParent()->porttype = pt_gcs;
	GetParent()->ready = true;

	// Grab system hostname
	char* hostname = new char[MAX_PATH];
	XCGetHostName( hostname, MAX_PATH );

	// Send a contact UI message to GCS
	disp_message* disp_info;
	XCToolsMessage msg_send( this ); 

	msg_send.CreateMsg( DISP_MESSAGE_SIZE + STARTUP_MESSAGE_SIZE + strlen( hostname ) + strlen(process) + 1 );
	msg_send.SetHeader( msg_dispatch, src_int, dest_gcs, false, false );

	disp_info = (disp_message*)msg_send.GetDataPtr(); 
	disp_info->opcode = disp_contact_ui;

	startup_message* startup = (startup_message*)disp_info->data;
	startup->appnum = myappnum;
	startup->pid = XCGetProcessIdentifier();
	startup->filelen = strlen( process );
	sprintf( startup->data, "%s%s", process, hostname );
	delete [] hostname;

	SendMessage( msg_src, GetParent(), &msg_send );

	// Send a status message to the UI
	SendStatus( src_int, GetParent(), listen_port );

	OUTFUNC();
};



void XCXMLAppInterface::OnUserMessage( char* message ) 
{ 
//	cout << "[XCXMLAPPINTERFACE::ONUSERMESSAGE]" << endl;
    printf( "UserMessage: %s\n", message ); 
};




XCTransactionInfo* XCXMLAppInterface::OnTransaction( XCTransactionInfo* transaction ) {
	INFUNC( XCXMLAppInterface::OnTransaction, this );

	const static char severity[4][10] = { "Status", "Warning", "Error", "Critical" };

	// Find the socket associated with this ID
	int app_nbr;
	trans_socket_type* socket_trans = FindSocketTrans( transaction->GetTransID(), app_nbr );

	// If none is found, just exit, must have closed the socket
	if (!socket_trans)
		return NULL;

	XCSocketTransport* cursocket = socket_trans->socket->transport;

	crit.Lock();

	// Send a debug message
	char stemp[ 255 ];
	sprintf( stemp, "Txn Resp: txn:%ld, id:%ld, count:%ld\n", transaction->GetTransNumber(), transaction->GetTransID(), transaction->GetCount());
	Protea::DebugOutput( stemp );

//cout << endl << "[XCXMLAPPINTERFACE::ONTRANSACTION]" << endl;
	char* outbuf = new char[ MAX_LEN ];
	char* messagebuf = new char[ MAX_LEN ];
	bool messagesexist = false;
	bool buffersexist = false;
	int loc;

	if (timer)
		timer->Stop();

	try {
		// Output transaction response header
		loc = sprintf( outbuf, 
			"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
			"<eXcalibur Version=\"1.0\">\n" 
			"\t<TransactionList ApplNumber=\"%ld\">\n"
			"\t\t<Transaction Number=\"%ld\" ID=\"%ld\" Session=\"%d\" Success=\"%ld\"",
			transaction->GetApplNumber(),
			transaction->GetTransNumber(),
			transaction->GetTransID(), 
			transaction->GetSession(),
			(long)transaction->GetSuccess()
		);

		if ( transaction->GetSuperID() )
			loc += sprintf( outbuf + loc, " Super=\"%ld\"", transaction->GetSuperID() );
		if ( transaction->GetPhase() )
			loc += sprintf( outbuf + loc, " Phase=\"%d\"", (int)transaction->GetPhase() );
		loc += sprintf( outbuf + loc, ">\n" );

		// Create the message response
		sprintf( messagebuf, "\t\t\t<MessageList>\n" );
		XCMessageList* temp = transaction->GetFirstMessage();
		long buffercount = 0;
		while (temp) {
			if ( loc ) {
				xprintstr( false, cursocket, outbuf, loc );
				loc = 0;
			};

			memset( outbuf, 0, MAX_LEN );
			XCToolsMessage* msg = temp->msg;
			base_message* header = msg->GetHeaderPtr();

			ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Message: %x", msg );
			ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Interface Received Message : %ld", header->type );

			trans_message* trans_info = (trans_message*)msg->GetDataPtr();
			if ( trans_info->data_type == TRANS_HEADER_TYPE || trans_info->data_type == TRANS_DETAIL_TYPE ) {
				// Do we need the bufferlist tag?
				if (!buffersexist) {
					buffersexist = true;
					loc += sprintf( outbuf + loc, "\t\t\t<BufferList>\n" );
				};

				// Create a buffer from the message data
				XCAppInterface* app = GetApplication( app_nbr );
				XCBuffer* buf = app->CreateDataBuffer( trans_info->format, trans_info->data );

				loc += sprintf( outbuf + loc, "\t\t\t\t<Buffer Number=\"%ld\" Order=\"%ld\" ", trans_info->format, buffercount );
				if (trans_info->data_type == TRANS_HEADER_TYPE) 
					loc += sprintf( outbuf + loc, "Type=\"Header\">\n" );
				else
					loc += sprintf( outbuf + loc, "Type=\"Detail\">\n" );

				for (int i=0; i<buf->GetFieldCount(); i++) {
					XCField* fld = buf->GetFieldByIndex( i );
					char* temp = fld->GetFieldName();
					long fldnum = fld->GetFieldNumber();

					// Output the data
					bool keep = true;
					int prevloc = loc;
					char *BLOBbase64;
					long BLOBbase64size;

					loc += sprintf( outbuf + loc, "\t\t\t\t\t<Field Number=\"%ld\" Name=\"%s\"", fldnum, temp );
					if( fld->GetType() == XCBLOB ) 
						loc += sprintf( outbuf + loc, " Encoding=\"base64\"" );				
					loc += sprintf( outbuf + loc, ">" );

					switch( fld->GetType() ) {
						case S_INTEGER:
						case D_INTEGER:
							loc += sprintf( outbuf + loc, "%ld", (long)fld->GetLong( true ) );
							break;

						case DEC_FLOAT:
						case DEC_DOUBLE:
						case DATE_JULIAN:
						case DECIMAL_64:
						case LITERAL_VAL:
							keep = false;
							break;

						case XCBLOB:
							BLOBbase64size = fld->GetBLOBbase64Size();
							if(BLOBbase64size > 0 ) {
								BLOBbase64 = new char[BLOBbase64size];
								fld->GetBLOBbase64( BLOBbase64, BLOBbase64size );
								loc += sprintf( outbuf + loc, "%s", BLOBbase64 );
								delete [] BLOBbase64;
							}
							break;

						// Assume string value
						default: {
							char* data = fld->GetOffset();

							// Eliminate preceeding white-space
							while (*data == ' ')
								data++;

							// Eliminate trailing white-space
							char* end = data;
							while (*end)
								end++;
							end--;
							while (end >= data && *end == ' ')
								end--;
							*(end+1) = 0;

							// Fix Data Nicely, please.
							data = FixEscape( data );

							// output the data
							if (*data)
								loc += sprintf( outbuf + loc, "%s", data );

							delete [] data;
						};	// default case
					};	// switch

					if (keep)
						loc += sprintf( outbuf + loc, "</Field>\n" );
					else
						loc = prevloc;
				};
				buf->Release();
				loc += sprintf( outbuf + loc, "\t\t\t\t</Buffer>\n" );
				buffercount++;
			} else if (trans_info->data_type == TRANS_MESSAGE_TYPE) {
				// We have a transaction message
				if ( !messagesexist ) {
					messagesexist = true;
					sprintf( messagebuf, "\t\t\t<MessageList>\n" );
				};

				trans_error_message* error = (trans_error_message*)trans_info->data;

				// Fix Data Nicely, please.
				char* data = FixEscape( error->text );
				sprintf( messagebuf, "%s\t\t\t\t<Message Number=\"%ld\" Severity=\"%s\" Field=\"%ld\" Order=\"%ld\">%s</Message>\n", messagebuf, error->number, severity[ error->severity+1 ], (long)error->fieldnumber, trans_info->format, data );
				delete [] data;
			} else {
				// Who knows what we got
				loc += sprintf( outbuf+loc, "UNKNOWN MESSAGE RECEIVED!\n" );
			};

			temp = temp->next;
		};
		if ( buffersexist )
			loc += sprintf( outbuf+loc, "\t\t\t</BufferList>\n" );
		if ( messagesexist )
			loc += sprintf( outbuf+loc, "%s\t\t\t</MessageList>\n", messagebuf );

		// Finish out the xml...
		loc += sprintf( outbuf+loc, "\t\t</Transaction>\n" );
		loc += sprintf( outbuf+loc, "\t</TransactionList>\n" );
		loc += sprintf( outbuf+loc, "</eXcalibur>\n" );

	//	printf( "Write to socket!\n" );
		xprintstr( true, cursocket, outbuf, loc );
	} catch( XCToolsError& error ) {
		printf( "ERROR: %s\n", error.GetDescription () );
	} catch( ... ) {
		// Ignore errors sending?
		printf( "ERROR SENDING DATA\n" );
	};

	cursocket = NULL;

	delete [] outbuf;
	delete [] messagebuf;

//	printf( "Release lock\n" );
	crit.UnLock();

	RemoveSocketTrans( socket_trans );

	sprintf( stemp, "Txn Resp Complete: txn:%d, id:%d\n", transaction->GetTransNumber(), transaction->GetTransID());
	Protea::DebugOutput( stemp );

	OUTFUNCRET( NULL );
};



XCTransactionInfo* XCXMLAppInterface::OnQuery( XCTransactionInfo* transaction ) {
	INFUNC( XCXMLAppInterface::OnQuery, this );

	// Find the socket associated with this ID
	int app_nbr;
	trans_socket_type* socket_trans = FindSocketTrans( transaction->GetTransID(), app_nbr );

	// If none if found, just exit, must have closed the socket
	if (!socket_trans)
		return NULL;

	XCSocketTransport* cursocket = socket_trans->socket->transport;

	crit.Lock();

	try {
		//cout << endl << "[XCXMLAPPINTERFACE::ONQUERY]" << endl;
		char outbuf[ MAX_LEN ];
		int loc;

		if (timer)
			timer->Stop();

		sql_query_header_message* header = (sql_query_header_message*)transaction->GetHeader()->GetDataPtr();

		loc = sprintf( outbuf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<eXcalibur Version=\"1.0\">\n" );
		loc += sprintf( outbuf + loc, "\t<QueryResult ID=\"%ld\" Count=\"%ld\">\n", transaction->GetTransID(), transaction->GetRecordCount() );

		XCMessageList* temp = transaction->GetFirstMessage();
		while (temp) {
			XCToolsMessage* msg = temp->msg;
			if (msg->GetHeaderPtr()->type == msg_sql_query) {
				sql_query_result_message* message = (sql_query_result_message*)msg->GetDataPtr();
				if (message->data_type == QUERY_FIELD_INFO) {
					loc += sprintf( loc+outbuf, "\t\t\t<Record>\n" );
					xprintstr( false, cursocket, outbuf, loc );
					loc = 0;
					memset( &outbuf, 0, MAX_LEN );

					// Output individual fields now
					XCAppInterface* app = GetApplication( app_nbr );
					char* data = message->data;
					for (int i=0; i<message->fieldcount; i++) {
						sql_query_field* fld = (sql_query_field*)data;
						if (fld->number) {
							buffer_field* fld_name = app->GetFieldInfo( fld->number );
							if (fld_name) {
								loc += sprintf( outbuf+loc, "\t\t\t\t<Field Name='%s' Number='%d' Type='%ld' Value='%s' />\n", 
									fld_name->name, 
									fld->number,
									fld_name->type,
									fld->data );
							} else {
								loc += sprintf( outbuf+loc, "\t\t\t\t<Field Number='%d' Value='%s' />\n", 
									fld->number,
									fld->data );
							};
						};
						data += sizeof( sql_query_field ) + fld->valuesize;
					};

					loc += sprintf( outbuf+loc, "\t\t\t</Record>\n" );
				};
			};
			temp = temp->next;
		};
		loc += sprintf( outbuf+loc, "\t</QueryResult>\n" );
		loc += sprintf( outbuf+loc, "</eXcalibur>\n" );

	//	printf( "Write to socket!\n" );
		xprintstr( true, cursocket, outbuf, loc );

		cursocket = NULL;
	} catch( ... ) {
		// Always release our lock
		crit.UnLock();

		// Rethrow the error
		throw;
	};

//	printf( "Release lock\n" );
	crit.UnLock();

	RemoveSocketTrans( socket_trans );

	OUTFUNCRET( NULL );
};





void XCXMLAppInterface::SendTransactionXML( socket_information* info, XCTransactionInfo* txn ) {
	// Associate this socket with this transaction ID
	AddSocketTrans( info, txn->GetTransID(), txn->GetApplNumber(), txn->GetSession() );

	// Send a debug message
	char temp[ 255 ];
	sprintf( temp, "Send Txn: txn:%d, id:%d\n", txn->GetTransNumber(), txn->GetTransID());
	Protea::DebugOutput( temp );

	long app = txn->GetApplNumber();
	SendTransaction( txn, GetParent(), NULL, txn->GetApplNumber() );
};



void XCXMLAppInterface::OnDroppedConnection( XCMsgPortPtr port ) {
	if (port == parent) {
		// We lost the parent, die as gracefully as possible, by closing the listen_socket
		listen_socket->Close();
	};
};




//
// Socket transaction storage
//
void XCXMLAppInterface::AddSocketTrans( socket_information* info, int trans_id, int app_nbr, int session ) {
	// Lock our socket information
	socket_info.Lock();

	try {
		trans_socket_type* tst = new trans_socket_type;

		tst->app_nbr = app_nbr;
		tst->socket = info;
		tst->trans_id = trans_id;
		tst->session = session;

		info->counter++;

		// Add ourselves to the list
		tst->next = trans_socket_list;
		tst->prev = NULL;
		if (trans_socket_list)
			trans_socket_list->prev = tst;
		trans_socket_list = tst;
	} catch( ... ) {
		socket_info.UnLock();
		throw;
	};
	socket_info.UnLock();
};



void XCXMLAppInterface::RemoveSocketTrans( trans_socket_type* temp ) {
	// Remove our reference to the socket, done here because would create a deadlock
	// condition if inside the loop
	RemoveSocketInfo( temp->socket );

	// Free memory
	delete temp;
};



// This function is called when a socket closes.  Sends an abort message to the system
// for each open transaction
//
void XCXMLAppInterface::AbortSocketTrans( socket_information* info ) {
	// Lock our socket information
	socket_info.Lock();

	trans_socket_type* temp = trans_socket_list;
	while (temp) {
		// If this transaction is associated with the closing socket:
		if (temp->socket == info) {
			XCAppInterface* app = NULL;
			XCTransactionInfo* trans = NULL;

			// Obtain a pointer to the applicatoin, and create a transaction object
			GetApplicationTransaction( app, temp->app_nbr, trans );

			// Setup the transaction request
			trans->SetPhase( 0 );
			trans->SetTransID( rand() );
			trans->SetTransNumber( -3 );
			trans->SetUser( "System" );
			trans->SetSession( temp->session );
			if (temp->app_nbr == GCS_APP_NUMBER)
				trans->SetGCSTrans( true );
			trans->SetLang( 0 );
			trans->SetSuperID( temp->trans_id );
			trans->SetAction( 0 );

			// Mark this transaction as a job, which means no password required
			// otherwise, we would have to stash the user/password for each transaction
			trans->SetJob();

			// Send the transaction
			SendTransactionXML( info, trans );
		};

		// Move on to the next item in the list
		temp = temp->next;
	};

	// Unlock the socket information
	socket_info.UnLock();
};


trans_socket_type* XCXMLAppInterface::FindSocketTrans( int trans_id, int& app_nbr ) {
	trans_socket_type* temp = NULL;

	// Lock the socket information structures
	socket_info.Lock();
	try {
		temp = trans_socket_list;
		while (temp && temp->trans_id != trans_id)
			temp = temp->next;

		if (temp) {
			// Set our return data
			app_nbr = temp->app_nbr;

			// Remove ourselves from the list
			if (temp->prev) {
				temp->prev->next = temp->next;
			} else
				trans_socket_list = temp->next;
		};
	} catch( ... ) {
		socket_info.UnLock();
		throw;
	};
	socket_info.UnLock();

	return temp;
};



/////////////////////////
//
// Application storage
//
/////////////////////////
XCAppInterface* XCXMLAppInterface::GetApplication( int app_nbr ) {
	XCAppInterface* result = NULL;

	bool nolist = false;
	app_list_crit.Lock();
	try {
		if (app_nbr == 1 || app_nbr == -1)
			result = this;
		else {
			XCXMLApplicationList* temp = app_list;
			if (!temp)
			{
				nolist = true;
				printf( "APP LIST NOT FOUND, RESOLVING\n" );
			}
			while (temp && temp->app->GetAppNumber() != app_nbr)
				temp = temp->next;

			if (!temp) {
				if (!nolist)
				{
					printf( "APP NOT FOUND IN LIST\n" );
				}

				result = new XCAppInterface();
				temp = new XCXMLApplicationList;
				temp->next = app_list;
				temp->app = result;
				app_list = temp;

				XCAppOptions myopts;
				result->GetOptions( myopts );
				myopts.debug_type_filter = 0;
				myopts.debug_level_cutoff[ TCL_TRAFFIC ] = TLV_EVERYTHING;
				myopts.debug_level_cutoff[ TCL_APPINTERFACE ] = TLV_ALWAYS;
				myopts.debug_level_cutoff[ TCL_MESSAGING ] = TLV_EVERYTHING;
				myopts.debug_level_cutoff[ TCL_FUNCTIONS ] = TLV_EVERYTHING;
				myopts.debug_level_cutoff[ TCL_BUFFER ] = TLV_HIGH;
				myopts.debug_level_cutoff[ TCL_NOLABEL ] = TLV_HIGH;
				myopts.no_database = true;
				myopts.no_transactions = true;
				result->SetOptions( myopts );

				// Initialize the application
				result->Initialize( "XMLINT_APPLICATION", app_nbr );
			} else
				result = temp->app;
		};
	} catch( ... ) {
		app_list_crit.UnLock();
		throw;
	};
	app_list_crit.UnLock();
	return result;
};


void XCXMLAppInterface::GetApplicationTransaction( XCAppInterface* &app, int app_nbr, XCTransactionInfo* &info ) {
	if (app_nbr) {
		// Get a pointer to the application
		app = GetApplication( app_nbr );

		// Create the transaction information class
		info = new XCTransactionInfo( app, NULL );
	} else {
		app = NULL;
		info = NULL;
		throw XCToolsError( ERR_NO_APPLICATION, __LINE__, NULL, 0, 0, 0 );
	};
};




void XCXMLAppInterface::AddSocketInfo( socket_information* info ) {
	// Obtain a lock on the socket/thread information
	socket_info.Lock();

	try {
		// Add ourselves to the list
		info->next = socket_thread_list;
		info->prev = NULL;
		if (socket_thread_list)
			socket_thread_list->prev = info;
		socket_thread_list = info;

		// Set our counter
		info->counter = 1;
	} catch( ... ) {
		// Protect our critical section from failure
		socket_info.UnLock();
		throw;
	};
	socket_info.UnLock();
};


void XCXMLAppInterface::RemoveSocketInfo( socket_information* info ) {
	// Obtain a lock on the socket information
	socket_info.Lock();

	try {
		// Decrement our counter
		info->counter--;

		// Free the socket information if no longer in use
		if (!info->counter) {
			// Delete the socket object
			if (info->transport)
				delete info->transport;

			// Remove ourselves from the list
			if (info->prev) {
				info->prev->next = info->next;
			} else
				socket_thread_list = info->next;

			// Free memory
			delete info;
		};
	} catch( ... ) {
		// Protect our critical section from failure
		socket_info.UnLock();
		throw;
	};
	socket_info.UnLock();
};



void XCXMLAppInterface::HandleSecurityChange( XCToolsMessage* msg ) {
	// Do nothing, ignore
};
